/** */
package co.com.umobile.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.json.JSONException;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Message;
import android.util.Log;
import android.widget.RemoteViews;
import co.com.umobile.R;
import co.com.umobile.application.GlobalState;
import co.com.umobile.common.Numbers;
import co.com.umobile.common.ResponseCodes;
import co.com.umobile.common.util.ServicesCodes;
import co.com.umobile.common.widget.ProgressHandler;
import co.com.umobile.dao.GenericDao;
import co.com.umobile.domain.ServiceResponse;
import co.com.umobile.domain.User;
import co.com.umobile.gui.LoginActivity;
import co.com.umobile.gui.UpdatesActivity;
import co.com.umobile.network.SSLSocketFactory;
import co.com.umobile.persistence.DatabaseManager;
import co.com.umobile.persistence.ProviderUtilities;


/**
 * @author sergiogranada
 * 
 */
public class GenericSyncAdapter extends AbstractThreadedSyncAdapter {

  /** the flag for sync operations. */
  public static final String SYNC_FINISHED = "SYNC_FINISHED";

  /** the flag for sync operations. */
  public static final String SYNC_INIT = "SYNC_INIT";
  
  /** the application context. */
  private Context context;

  /** the application singleton to keep data within the app. */
  private GlobalState globalState;


  /** the notification bar. */
  private Notification notification;

  /** the notification manager. */
  private NotificationManager notificationManager;

  /** the progress bar handler. */
  private ProgressHandler progressHandler = null;

  /** message used by the progress handler. */
  private Message message = null;

  /** the serviceCode. */
  private int serviceCode;

  /** the service name. */
  private String serviceName;

  /** the custom implementation of the DAO. */
  private GenericDao genericDao;

  /** the user account manager. */
  private AccountManager mAccountManager;

  /** the service urls. */
  private String[] urls;

  private User userSession;
  /**
   * Initial constructor.
   * @param context context.
   * @param globalState globalState.
   * @param serviceCode identifier of the service.
   * @param serviceName identifier of the user into server.
   * @param genericDao define the interface of the services.
   * @param virtualTable table.
   */
  public GenericSyncAdapter(Context context, GlobalState globalState, int serviceCode, String serviceName,
      GenericDao genericDao) {
    super(context, true);
    this.context = context;
    this.globalState = globalState;
    this.serviceCode = serviceCode;
    this.genericDao = genericDao;
    this.serviceName = serviceName;
    userSession = globalState.getLastUserSession();
    globalState.setUserSession(userSession);
    mAccountManager = AccountManager.get(context);

  }

  /*
   * (non-Javadoc)
   * 
   * @see android.content.AbstractThreadedSyncAdapter#onPerformSync(android.accounts.Account,
   * android.os.Bundle, java.lang.String, android.content.ContentProviderClient, android.content.SyncResult)
   */
  @Override
  public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
      SyncResult syncResult) {
    String authtoken = null;
    if (globalState == null || globalState.getUserSession() == null) {
      return;
    }
    
    urls = ServicesCodes.getServicesUrlsByServiceCode(serviceCode, context);

    try {
      authtoken = mAccountManager.blockingGetAuthToken(account, LoginActivity.PARAM_AUTHTOKEN_TYPE, true);
      consumeService();
//    } catch (OperationCanceledException e) {
//      handleException(authtoken, e, syncResult);
//    } catch (AuthenticatorException e) {
//      handleException(authtoken, e, syncResult);
    } catch (IOException e) {
      handleException(authtoken, e, syncResult);
    } catch (InvalidCipherTextException e) {
      handleException(authtoken, e, syncResult);
    } catch (NameNotFoundException e) {
      handleException(authtoken, e, syncResult);
//    } catch (CustomException e) {
//      handleException(authtoken, e, syncResult);
    } catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} finally {
      globalState.getHandlerByService().remove(serviceCode);
      Intent intentSync = new Intent(SYNC_INIT);
       intentSync.putExtra("serviceCode", serviceCode);
      getContext().sendBroadcast(intentSync);
    }

  }

  /**
   * handles the update service exceptions.
   * 
   * @param authtoken token of authentications.
   * @param e exception.
   * @param syncResult result of sync.
   */
  private void handleException(String authtoken, Exception e, SyncResult syncResult) {
    //SSFMOBILE-138
    globalState.getUpdateQueue().clear();
    globalState.getHandlerByService().clear();
    
    String tag = "SSF EXCEPTION:";

    if (e instanceof AuthenticatorException) {
      syncResult.stats.numParseExceptions++;
      Log.e(tag, "AuthenticatorException", e);
    } else if (e instanceof OperationCanceledException) {
      Log.e(tag, "OperationCanceledExcepion", e);
    } else if (e instanceof IOException) {
      Log.e(tag, "IOException", e);
      syncResult.stats.numIoExceptions++;
    } else if (e instanceof AuthenticationException) {
      mAccountManager.invalidateAuthToken(ProviderUtilities.ACCOUNT_TYPE, authtoken);
      syncResult.stats.numIoExceptions++;
      Log.e(tag, "AuthenticationException", e);
    } else if (e instanceof ParseException) {
      syncResult.stats.numParseExceptions++;
      Log.e(tag, "ParseException", e);
    } else if (e instanceof JSONException) {
      syncResult.stats.numParseExceptions++;
      Log.e(tag, "JSONException", e);
    }
  }

  /**
   * performs an update.
 * @throws Exception 
   * 
   * @throws CustomException CustomException.
   */
  public void consumeService() throws Exception {

    PendingIntent pendingIntent = null;
    ServiceResponse serviceResponse = null;

    try {
      DatabaseManager.getDatabase().beginTransactionNonExclusive();
      notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

      Intent configIntent = new Intent(context, UpdatesActivity.class);
      pendingIntent = PendingIntent.getActivity(context, 0, configIntent, 0);

//      UpdateEntity updateEntity = UpdatesDAO.getUpdateEntity(userSession.getUserName(), userSession.getCompany(),
//          serviceCode);
//      String lastDate = "null";
//      if (updateEntity != null) {
//        lastDate = String.valueOf(updateEntity.getDateUpdate().longValue());
//      }

      for (int i = 0; i < urls.length; i++) {

        serviceResponse = null;
        serviceResponse = getDataFromServer(i + 1, urls.length, urls[i]);

        if (serviceResponse == null) {
          serviceResponse = getServiceError(null);
          throw new Exception();
//          throw new CustomException(serviceResponse.getCode(), serviceResponse.getDescription());
        } else if (!serviceResponse.getCode().equals(ResponseCodes.SUCCESSFUL)) {
        	throw new Exception();
//          throw new CustomException(serviceResponse.getCode(), serviceResponse.getDescription());
        }

      }

//      UpdatesDAO.updateDate(userSession.getUserName(), userSession.getCompany(), serviceResponse.getLastUpdate() !=  null 
//          ? Long.parseLong(serviceResponse.getLastUpdate()) : System.currentTimeMillis(), serviceCode);

      DatabaseManager.getDatabase().setTransactionSuccessful();

    } finally {

      DatabaseManager.getDatabase().endTransaction();

      if (notificationManager != null) {
        notificationManager.cancel(Numbers.FORTY_TWO);
      }

      getProgressHandler();

      if (serviceResponse != null && serviceResponse.getCode().equals(ResponseCodes.SUCCESSFUL)) {

//        UpdateEntity updateEntity = UpdatesDAO.getUpdateEntity(userSession.getUserName(), userSession.getCompany(),
//            serviceCode);

        String textFormats = "yyyy-MM-dd HH:mm";
        SimpleDateFormat sdf = new SimpleDateFormat(textFormats);
        sdf.setTimeZone(Calendar.getInstance().getTimeZone());
        String updateDate = String.format(context.getResources().getString(R.string.last_update_message), serviceName);

        if (globalState != null  && globalState.getHandlerByService() != null && globalState.getHandlerByService().get(serviceCode) != null) {
          progressHandler = globalState.getHandlerByService().get(serviceCode);
        
          message = progressHandler.obtainMessage(Numbers.THREE, 0, 1, updateDate);
          message.sendToTarget();
        }
        showNotificationMessage(pendingIntent, updateDate);

      } else {

        String errorMessage = String.format(context.getResources().getString(R.string.last_update_message_error),
            serviceName);

        if (globalState != null && progressHandler != null) {
          progressHandler = globalState.getHandlerByService().get(serviceCode);

          message = progressHandler.obtainMessage(Numbers.THREE, 0, 2, errorMessage);
          message.sendToTarget();
        }
        showNotificationMessage(pendingIntent, errorMessage);

      }

      // remove the notification (we're done)
      // notificationManager.cancel(42);

      if (serviceResponse == null) {
        serviceResponse = getServiceError(null);
        throw new Exception();
//        throw new CustomException(serviceResponse.getCode(), serviceResponse.getDescription());
      } else if (!serviceResponse.getCode().equals(ResponseCodes.SUCCESSFUL)) {
    	  throw new Exception();
//        throw new CustomException(serviceResponse.getCode(), serviceResponse.getDescription());
      }

    }

  }

  /**
   * shows a notification message.
   * 
   * @param pendingIntent pendingIntent.
   * @param textMessage message.
   */
  private void showNotificationMessage(PendingIntent pendingIntent, String textMessage) {
    notification = new Notification(R.drawable.u_m_logo, String.format(
        context.getResources().getString(R.string.update_result_notification), serviceName), System.currentTimeMillis());

    notification.defaults |= Notification.DEFAULT_SOUND;
    notification.defaults |= Notification.DEFAULT_VIBRATE;
    notification.defaults |= Notification.DEFAULT_LIGHTS;

    notification.flags = notification.flags | Notification.FLAG_AUTO_CANCEL;
    notification.contentView = new RemoteViews(context.getPackageName(), R.layout.download_result_notification);
    notification.contentIntent = pendingIntent;
    notification.contentView.setImageViewResource(R.id.status_icon, R.drawable.u_m_logo);
    notification.contentView.setTextViewText(R.id.status_text, textMessage);
    // inform the progress bar of updates in progress
    notificationManager.notify(Numbers.FORTY_TWO, notification);
  }

  /**
   * gets the data from server.
   * @param lastDate las update date.
   * @param serviceNumber number of service.
   * @param serviceSize size of the service.
   * @param url url connection.
   * @param virtualTable virtual table.
   * @return ServiceResponse.
   * @throws InvalidCipherTextException InvalidCipherTextException.
   * @throws NameNotFoundException NameNotFoundException.
   * @throws IOException IOException.
   */
  private ServiceResponse getDataFromServer(int serviceNumber, int serviceSize, String url)
		  throws InvalidCipherTextException, NameNotFoundException, IOException {

    updateProgressBarIndeterminate(String.format(context.getResources().getString(R.string.updating_notification),
        serviceName, String.valueOf(serviceNumber), String.valueOf(serviceSize)));

    ServiceResponse serviceResponse = null;

    HttpResponse response = getHttpResponse(url);

    Long responseSize = (response.getFirstHeader("cLength") != null) ? Long.parseLong(response
        .getFirstHeader("cLength").getValue()) : null;

    if (response.getStatusLine().getStatusCode() != Numbers.TWO_HUNDRED || responseSize == null) {
      return getServiceError(response);
    } else {
      if (responseSize.longValue() == 0) {
        serviceResponse = getServiceResponseSuccessful(response);
        return serviceResponse;
      }
    }

    InputStream instream = response.getEntity().getContent();
    Header contentEncoding = response.getFirstHeader("Content-Encoding");
    if (contentEncoding != null && ("gzip").equalsIgnoreCase(contentEncoding.getValue())) {
      instream = new GZIPInputStream(instream);

      InputStreamReader reader = new InputStreamReader(instream);
      BufferedReader in = new BufferedReader(reader);
      
      updateListWithProgress(responseSize, in, serviceNumber, serviceSize);

      updateProgressBarIndeterminate(String.format(context.getResources().getString(R.string.updating_notification),
          serviceName, String.valueOf(serviceNumber), String.valueOf(serviceSize)));

      serviceResponse = getServiceResponseSuccessful(response);

      return serviceResponse;
    } else {
      return getServiceError(response);
    }

  }

  /**
   * updates the order type table using a progress bar (updates within the app).
   * @param responseSize response size to set.
   * @param in {@link BufferedReader} in.
   * @param serviceNumber service number.
   * @param serviceSize service size.
   * @throws IOException An exception occurred.
   */
  private void updateListWithProgress(long responseSize, BufferedReader in, int serviceNumber, int serviceSize)
      throws IOException {

    showProgressBar();

    double incrementPercentage = Numbers.TWENTY;
    double increment = responseSize / incrementPercentage;
    long count = 0;
    double lastUpdate = increment;
    double percentage = 0;

    String updateProgress = String.format(context.getResources().getString(R.string.updating_progress), serviceName,
        String.valueOf(serviceNumber), String.valueOf(serviceSize));

    String line = null;
    while ((line = in.readLine()) != null) {

      genericDao.insertOrUpdateData(line, serviceNumber);
      count++;

      if (count > lastUpdate) {
        lastUpdate += increment;
        percentage += Numbers.FIVE;

        String notificationMessage = String.valueOf((int) percentage) + "% " + updateProgress;

        updateProgressBarIncrement(percentage, notificationMessage);
      }
    }

    fillProgressBar();

  }

  /**
   * fills the progress bar.
   */
  private void fillProgressBar() {
    if (progressHandler != null) {
      message = progressHandler.obtainMessage(2, Numbers.ONE_HUNDRED, 0);
      message.sendToTarget();
    }
  }

  /**
   * updates the progress bar.
   * 
   * @param percentage percentaqge.
   * @param notificationMessage notification.
   */
  private void updateProgressBarIncrement(double percentage, String notificationMessage) {
    if (globalState != null && globalState.getHandlerByService().get(serviceCode) != null) {
      progressHandler = globalState.getHandlerByService().get(serviceCode);
      message = progressHandler.obtainMessage(2, (int) percentage, 0, notificationMessage);
      progressHandler.setProgress((int) percentage);
      message.sendToTarget();
    }
  }

  /**
   * gets the current progress handler.
   */
  private void getProgressHandler() {
    if (globalState != null && globalState.getHandlerByService().get(serviceCode) != null && progressHandler == null) {
      progressHandler = globalState.getHandlerByService().get(serviceCode);
    }
  }

  /**
   * shows the progress bar.
   */
  private void showProgressBar() {
    if (globalState != null && globalState.getHandlerByService() != null) {
      progressHandler = globalState.getHandlerByService().get(serviceCode);

      if (progressHandler != null) {
        message = progressHandler.obtainMessage(1);
        message.sendToTarget();
      }
    }
  }

  /**
   * used for connection errors.
   * 
   * @param response http response.
   * @return {@link ServiceResponse} service response.
   */
  private ServiceResponse getServiceError(HttpResponse response) {

    ServiceResponse serviceResponse = null;
    String responseCode = null;
    String responseMessage = null;

    if (response != null && response.getStatusLine() != null
        && response.getStatusLine().getStatusCode() == ResponseCodes.CUSTOM_ERROR) {

      responseCode = response.getFirstHeader(ResponseCodes.ERROR_CODE).getValue();
      responseMessage = response.getFirstHeader(ResponseCodes.ERROR_MESSAGE).getValue();

    } else {
      responseCode = ResponseCodes.UNKNOWN_ERROR;
      responseMessage = context.getResources().getString(R.string.error_occurred);
    }

    serviceResponse = new ServiceResponse();
    serviceResponse.setCode(responseCode);
    serviceResponse.setDescription(responseMessage);

    return serviceResponse;
  }

  /**
   * used for a successful connection.
   * 
   * @param response {@link HttpResponse} to set.
   * @return ServiceResponse service response.
   */
  private ServiceResponse getServiceResponseSuccessful(HttpResponse response) {

    if (response != null && response.containsHeader("sid")) {
      userSession.setSessionId(response.getFirstHeader("sid").getValue());
//      UserSessionDAO.updateUser(userSession, context);
    }

    ServiceResponse serviceResponse = new ServiceResponse();
    serviceResponse.setCode(ResponseCodes.SUCCESSFUL);
    serviceResponse.setDescription(String.format(context.getResources().getString(R.string.successful_update),
        serviceName));
    return serviceResponse;
  }

  /**
   * Method that create http-response communication.
   * @param urlService url.
   * @return HttpResponse HttpResponse.
   * @throws InvalidCipherTextException .
   * @throws IOException .
   * @throws NameNotFoundException .
   */
  private HttpResponse getHttpResponse(String urlService) throws InvalidCipherTextException,
      IOException, NameNotFoundException {

    SchemeRegistry schemeRegistry = new SchemeRegistry();
    /*
     * // http scheme schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
     */
    // https scheme
    schemeRegistry.register(new Scheme("https", new SSLSocketFactory(), Integer.valueOf((((GlobalState) context
        .getApplicationContext()).getPort()).toString())));

    HttpParams httpParameters = new BasicHttpParams();
    // Set the timeout in milliseconds until a connection is established.
    // The default value is zero, that means the timeout is not used.
    int timeoutConnection = Numbers.TWENTY_THOUSAND;
    HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
    // Set the default socket timeout (SO_TIMEOUT)
    // in milliseconds which is the timeout for waiting for data.
    int timeoutSocket = Numbers.TWENTY_FIVE_THOUSAND;
    HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

    ThreadSafeClientConnManager clientConnectionManager = new ThreadSafeClientConnManager(httpParameters,
        schemeRegistry);

    HttpClient httpclient = new DefaultHttpClient(clientConnectionManager, httpParameters);
    int versionCode = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;

    // HttpPost request = new HttpPost("http://10.0.2.2:9090/SyncAsesorServer/rest/auth");
    HttpGet request = new HttpGet(getServiceUrl(urlService + "/" + String.valueOf(versionCode), userSession));

    request.setHeader("Accept", "text/plain");
    request.setHeader("Accept-Charset", "utf-8");

    request.addHeader("Content-Type", "application/octet-stream");
    request.addHeader("Accept-Encoding", "gzip");
    request.addHeader("Connection", "Keep-Alive");

    HttpResponse response = null;
    response = httpclient.execute(request);
    return response;
  }

  /**
   * adds the parameters to the url.
   * 
   * @param url url connection.
   * @param userSessionService user identifier.
   * @param lastDate last update.
   * @return String url.
   * @throws UnsupportedEncodingException UnsupportedEncodingException.
   * @throws InvalidCipherTextException InvalidCipherTextException.
   */
  private String getServiceUrl(String url, User userSessionService)
      throws InvalidCipherTextException, UnsupportedEncodingException {

    if (!url.endsWith("?")) {
      url += "?";
    }

    List<NameValuePair> params = new LinkedList<NameValuePair>();

    params.add(new BasicNameValuePair("usr", userSessionService.getUserCode()));
    params.add(new BasicNameValuePair("pwd", userSessionService.getPassword()));
    params.add(new BasicNameValuePair("ut", String.valueOf(userSessionService.getUserType())));
    params.add(new BasicNameValuePair("sid", userSessionService.getSessionId()));
    params.add(new BasicNameValuePair("da", String.valueOf(System.currentTimeMillis())));
    
    String paramString = URLEncodedUtils.format(params, "utf-8");

    url += paramString;
    return url;
  }

  /**
   * updates the progress bar.
   * 
   * @param notificationMessage message of notification.
   */
  private void updateProgressBarIndeterminate(String notificationMessage) {
    if (globalState != null && globalState.getHandlerByService().get(serviceCode) != null) {
      progressHandler = globalState.getHandlerByService().get(serviceCode);

      message = progressHandler.obtainMessage(Numbers.FIVE, notificationMessage);
      progressHandler.setProgress(0);
      message.sendToTarget();
    }
  }

  public void setUrls(String[] urls) {
    this.urls = urls;
  }
  
  

}
